#ifndef __HASH_FUNCTIONS_DOT_HPP__
#define __HASH_FUNCTIONS_DOT_HPP__
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
#include <assert.h>
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
#include <memline.hpp>
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
#include "../../polarssl-1.2.3/include/polarssl/md5.h"
#include "../../polarssl-1.2.3/include/polarssl/sha4.h"
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
union U512 {
    uint64_t      l[  8 ];
    unsigned char c[ 64 ];
};
union U128 {
    uint64_t      l[  2 ];
    unsigned char c[ 16 ];
};
union U192 {
    uint64_t      l[  3 ];
    unsigned char c[ 24 ];    
};
union LSU {
    uint64_t l;
    double d;
    struct {
        int32_t hi;
        int32_t lo;
    } s;
    struct {
        unsigned char b0;
        unsigned char b1;
        unsigned char b2;
        unsigned char b3;
        unsigned char b4;
        unsigned char b5;
        unsigned char b6;
        unsigned char b7;
    } bytes;
};
struct U128_compare {
    bool operator() ( const U128 & lhs, const U128 & rhs ) const {
        if( lhs.l[0] < rhs.l[0] ) { return true;  }
        if( lhs.l[0] > rhs.l[0] ) { return false; }
        if( lhs.l[1] < rhs.l[0] ) { return true;  }
        return false;
    }
};
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
class HashWrapper {
protected:
    uint64_t seed;
public:
    HashWrapper( const uint64_t _seed ) : seed( _seed ) {}
    virtual ~HashWrapper( void ) {}
    virtual inline uint64_t hash( const uint64_t k ) const { assert( false ); return 0ULL; }
    virtual inline uint64_t hash( const uint64_t * k, const uint32_t length ) const { assert( false ); return 0ULL; }
    inline void SetSeed( const uint64_t _seed ) { seed = _seed; }
};

class JenkinsHash : public HashWrapper {
private:
    // const uint64_t seed;
    inline void jhashmix( uint64_t & a, uint64_t & b, uint64_t & c ) const {
        a -= b; a -= c; a ^= ( c >> 43 );
        b -= c; b -= a; b ^= ( a <<  9 );
        c -= a; c -= b; c ^= ( b >>  8 );
        a -= b; a -= c; a ^= ( c >> 38 );
        b -= c; b -= a; b ^= ( a << 23 );
        c -= a; c -= b; c ^= ( b >>  5 );
        a -= b; a -= c; a ^= ( c >> 35 );
        b -= c; b -= a; b ^= ( a << 49 );
        c -= a; c -= b; c ^= ( b >> 11 );
        a -= b; a -= c; a ^= ( c >> 12 );
        b -= c; b -= a; b ^= ( a << 18 );
        c -= a; c -= b; c ^= ( b >> 22 );
    }
public:
    JenkinsHash( const uint64_t _seed = 0x9ddfea08eb382d69ULL ) : HashWrapper( _seed ) {
        #ifdef __FIS_DEBUG_COMPILE__
        printf( "JenkinsHash( seed = 0x%016lx ): constructed\n", seed );
        #endif
    }
    inline uint64_t hash( const uint64_t k ) const {
        // Bob Jenkins' hash function
        // http://burtleburtle.net/bob/c/lookup8.c
        uint64_t a;
        uint64_t b;
        uint64_t c;

        a = ( seed ^ k);
        b = (~( seed ^ k )) << 32;
        c = 0x9e3779b97f4a7c13LL;   // the golden ratio; an arbitrary value
            
        jhashmix( a, b, c );
        
        return c;
    }
    inline uint64_t hash( const uint64_t * k, const uint32_t length ) const {
        uint32_t len = length;

        uint64_t a;
        uint64_t b;
        uint64_t c;

        a = b = 0x2000;
        c = 0x9e3779b97f4a7c13LL;   // the golden ratio; an arbitrary value
            
        // handle most of the key
        while( len >= 3 ) {
            
            a += ( seed ^ k[ 0 ] );
            b += ( seed ^ k[ 1 ] );
            c += ( seed ^ k[ 2 ] );
            jhashmix( a, b, c );
            k   += 3;
            len -= 3;
        }
        
        // handle the last 2 ub8's
        c += ( length << 3) ;
        switch( len ) {
            // all the case statements fall through
            // c is reserved for the length */
            case  2: b += ( seed ^ k[ 1 ] );
            case  1: a += ( seed ^ k[ 0 ] );
            // case 0: nothing left to add
        }
        jhashmix( a, b, c );
        
        return c;
    }
};

class JenkinsHashNonZero : public HashWrapper {
private:
    // const uint64_t seed;
    inline void jhashmix( uint64_t & a, uint64_t & b, uint64_t & c ) const {
        a -= b; a -= c; a ^= ( c >> 43 );
        b -= c; b -= a; b ^= ( a <<  9 );
        c -= a; c -= b; c ^= ( b >>  8 );
        a -= b; a -= c; a ^= ( c >> 38 );
        b -= c; b -= a; b ^= ( a << 23 );
        c -= a; c -= b; c ^= ( b >>  5 );
        a -= b; a -= c; a ^= ( c >> 35 );
        b -= c; b -= a; b ^= ( a << 49 );
        c -= a; c -= b; c ^= ( b >> 11 );
        a -= b; a -= c; a ^= ( c >> 12 );
        b -= c; b -= a; b ^= ( a << 18 );
        c -= a; c -= b; c ^= ( b >> 22 );
    }
public:
    JenkinsHashNonZero( const uint64_t _seed = 0x9ddfea08eb382d69ULL ) : HashWrapper( _seed ) {
        #ifdef __FIS_DEBUG_COMPILE__
        printf( "JenkinsHashNonZero( seed = 0x%016lx ): constructed\n", seed );
        #endif
    }
    inline uint64_t hash( const uint64_t k ) const {
        // Bob Jenkins' hash function
        // http://burtleburtle.net/bob/c/lookup8.c
        uint64_t a;
        uint64_t b;
        uint64_t c;

        a = ( seed ^ k);
        b = (~( seed ^ k )) << 32;
        c = 0x9e3779b97f4a7c13LL;   // the golden ratio; an arbitrary value
            
        jhashmix( a, b, c );
        
        if( c == 0ULL ) { c = 0xDEADBEEF; }
        return c;
    }
    inline uint64_t hash( const uint64_t * k, const uint32_t length ) const {
        uint32_t len = length;

        uint64_t a;
        uint64_t b;
        uint64_t c;

        a = b = 0x2000;
        c = 0x9e3779b97f4a7c13LL;   // the golden ratio; an arbitrary value
            
        // handle most of the key
        while( len >= 3 ) {
            
            a += ( seed ^ k[ 0 ] );
            b += ( seed ^ k[ 1 ] );
            c += ( seed ^ k[ 2 ] );
            jhashmix( a, b, c );
            k   += 3;
            len -= 3;
        }
        
        // handle the last 2 ub8's
        c += ( length << 3) ;
        switch( len ) {
            // all the case statements fall through
            // c is reserved for the length */
            case  2: b += ( seed ^ k[ 1 ] );
            case  1: a += ( seed ^ k[ 0 ] );
            // case 0: nothing left to add
        }
        jhashmix( a, b, c );
        
        if( c == 0ULL ) { c = 0xDEADBEEF; }
        return c;
    }
};

class SHA4HashWrapper : public HashWrapper {
    // polar ssl calls SHA2 as SHA4 (why??)
    inline void sha4wrapper( const unsigned char * p, uint64_t len, unsigned char * hash ) const {
        const size_t keylen = sizeof( uint64_t );
        const unsigned char * pseed = ( const unsigned char * ) &seed;
        sha4_hmac( pseed, keylen, p, len, hash,  0 );
    }
public:
    SHA4HashWrapper( const uint64_t _seed = 0x9ddfea08eb382d69ULL ) : HashWrapper( _seed ) {
        #ifdef LOCAL_DEBUG_ENABLED
        printf( "SHA4( seed = 0x%016lx ): constructed\n", seed );
        #endif
    }
    inline void hash( const uint64_t * message, const uint32_t length, line_t * hash ) const {
        const unsigned char * p = ( const unsigned char * ) message;
        sha4wrapper( p, length, hash->RawUCharPtr() );
    }
    inline uint64_t hash( const uint64_t * k, const uint32_t length ) const {

        uint64_t key = seed;
        unsigned char * pkey = (unsigned char *) &key;
        line_t x;
        
        sha4_hmac( pkey, 8, ( unsigned char * ) k, length*8, x.RawUCharPtr(), 0 );
        return x.d[0];
    }
};

class MD2HashWrapper : public HashWrapper {
    inline void md5wrapper( const unsigned char * p, uint64_t len, unsigned char * hash ) const {
        const size_t keylen = sizeof( uint64_t );
        const unsigned char * pseed = ( const unsigned char * ) &seed;
        md5_hmac( pseed, keylen, p, len, hash );
    }
public:
    MD2HashWrapper( const uint64_t _seed = 0x9ddfea08eb382d69ULL ) : HashWrapper( _seed ) {
        #ifdef LOCAL_DEBUG_ENABLED
        printf( "MD2( seed = 0x%016lx ): constructed\n", seed );
        #endif
    }
    inline void hash( const uint64_t * message, const uint32_t length, line_t * hash ) const {
        hash->SetToZero();
        const unsigned char * p = ( const unsigned char * ) message;
        md5wrapper( p, length, hash->RawUCharPtr() );
    }
};

#endif


